/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.model.dao.rdf;

import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Types;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;
import org.springframework.jdbc.support.GeneratedKeyHolder;

import net.groupme.exceptions.IDNotFoundException;
import net.groupme.exceptions.InsertFailedException;
import net.groupme.exceptions.UpdateFailedException;
import net.groupme.model.ObjectFactory;
import net.groupme.model.Resource;
import net.groupme.model.dao.DBProperties;
import net.groupme.utils.sql.GetStringFromTableWhereIDQuery;
import net.groupme.utils.sql.SQLUpdateUtility;

/**
 * created on Jun 27, 2007
 * 
 * {@link StatementDAO} implementation which operates on top of a MySQL database.
 * 
 * @author Fabian Abel, <a href="mailto:abel@l3s.de">abel@l3s.de</a>
 * @author last edited by: $Author: mischa $
 * 
 * @version $Revision: 1.6 $ $Date: 2007-10-23 17:10:03 $
 */
public class StatementDAOImpl implements StatementDAO {

	/** Name of the Table that stores a Statement */
	protected static final String TABLE_METADATA = DBProperties
			.getString("GroupmeDB.table.Metadata"); //$NON-NLS-1$
	
	
	/* TABLE AND ATTRIBUTE NAMES */ 
	
	/** Name of the attribute which defines the id of the Statement */
	protected static final String METADATA_ID = DBProperties
	.getString("GroupmeDB.Metadata.id"); //$NON-NLS-1$
	
	/** Name of the attribute which defines the Foreign Key to the Resource which is the 
	 * subject of a Statement */
	protected static final String METADATA_RESOURCE_ID = DBProperties
	.getString("GroupmeDB.Metadata.resourceID"); //$NON-NLS-1$

	/** Name of the attribute which defines the property of the Statement */
	protected static final String METADATA_PROPERTY = DBProperties
	.getString("GroupmeDB.Metadata.property"); //$NON-NLS-1$

	/** Name of the attribute which defines the object of the Statement */
	protected static final String METADATA_OBJECT = DBProperties
	.getString("GroupmeDB.Metadata.object"); //$NON-NLS-1$
	
	/** Name of the attribute which defines the datatype of the Statement's object */
	protected static final String METADATA_DATATYPE = DBProperties
	.getString("GroupmeDB.Metadata.datatype"); //$NON-NLS-1$
	
	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());
	
	/** the {@link DataSource} instance which answers the queries */
	private DataSource ds = null;

	/** the ID of the Statement */
	private final String id;
	
	/**
	 * Creates a new StatementDAOImpl instance.
	 * @param ds the {@link DataSource} which answers the queries
	 * @param subject the {@link Resource} which acts as subject of the Statement
	 * @param property the property (full URI)
	 * @param object the object of the Statement
	 * @param datatype the datatype of the object
	 * @throws InsertFailedException thrown if insert of new Statement failed.
	 */
	public StatementDAOImpl(DataSource ds, Resource subject, String property, String object, String datatype) throws IDNotFoundException, InsertFailedException{
		this.ds = ds;
		
		SqlUpdate su = new SqlUpdate(ds, "INSERT INTO " + TABLE_METADATA//$NON-NLS-1$
				+ " (" + METADATA_RESOURCE_ID + ", " + METADATA_PROPERTY + ", "
				+ METADATA_OBJECT + ", " + METADATA_DATATYPE + ") VALUES (?, ?, ?, ?)");
		su.declareParameter(new SqlParameter("sub", Types.VARCHAR)); //$NON-NLS-1$
		su.declareParameter(new SqlParameter("pred", Types.VARCHAR)); //$NON-NLS-1$
		su.declareParameter(new SqlParameter("obj", Types.VARCHAR)); //$NON-NLS-1$
		su.declareParameter(new SqlParameter("type", Types.VARCHAR)); //$NON-NLS-1$
		su.compile();
		String resID = null;
		resID = subject.getId().trim();
		if(resID.startsWith("#")){
			resID = resID.substring(1);
		}

		if(property == null){
			throw new InsertFailedException("Property must not to be null!");
		}
		if(object == null){
			throw new InsertFailedException("Object must not to be null!");
		}
		Object[] oa = {resID, property, object, datatype};
		
		GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
		int count = su.update(oa, keyHolder);
		if (count == 0){
			throw new InsertFailedException();
		}
		this.id = keyHolder.getKey().toString();
	}
	
	/**
	 * Creates a new StatementDAOImpl instance whereby the actual Statement,
	 * which is identified via the given id, must already exists within the database.
	 * in
	 * @param ds the {@link DataSource} which answers the queries
	 * @param id the ID of the (already existing) Statement 
	 * @throws IDNotFoundException thrown if the given ID (of the statement) does not exist in the Statement table
	 */
	public StatementDAOImpl(DataSource ds, String id) throws IDNotFoundException{
		this.ds = ds;
		this.id = id;
		
		GetStringFromTableWhereIDQuery getResourceIDQuery = new GetStringFromTableWhereIDQuery(
				ds, METADATA_RESOURCE_ID, TABLE_METADATA, id);
		if(!getResourceIDQuery.execute().iterator().hasNext()){
			throw new IDNotFoundException("There was no data set found in the " + TABLE_METADATA + " table which matches the given id '" + id + "'!");
		}
	}
	
	

	/* (non-Javadoc)
	 * @see net.groupme.model.Identifiable#getId()
	 */
	public String getId() {
		return this.id;
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.rdf.Statement#getDatatype()
	 */
	public String getDatatype() {
		return (String) new GetStringFromTableWhereIDQuery(
				ds, METADATA_DATATYPE, TABLE_METADATA, id).execute().iterator().next();
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.Identifiable#getDescription()
	 */
	public String getDescription() {
		return "Subject: " + this.getSubject().getURI() + " - Property: " + this.getProperty() 
			+ " - Object: " + this.getObject() + " ObjectDatatype: " + this.getDatatype();
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.Identifiable#getLabel()
	 */
	public String getLabel() {
		return this.getSubject().getURI() + " - " + this.getProperty() + " - " + this.getObject();
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.rdf.Statement#getObject()
	 */
	public String getObject() {
		return (String) new GetStringFromTableWhereIDQuery(
				ds, METADATA_OBJECT, TABLE_METADATA, id).execute().iterator().next();
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.rdf.Statement#getProperty()
	 */
	public String getProperty() {
		return (String) new GetStringFromTableWhereIDQuery(
				ds, METADATA_PROPERTY, TABLE_METADATA, id).execute().iterator().next();
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.rdf.Statement#getSubject()
	 */
	public Resource getSubject() {
		String resourceID = (String) new GetStringFromTableWhereIDQuery(
				ds, METADATA_RESOURCE_ID, TABLE_METADATA, id).execute().iterator().next();
		if(resourceID != null){
			return ObjectFactory.getResource(resourceID);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.Identifiable#getURI()
	 */
	public URI getURI() {
		try {
			return new URI("http://www.groupme.net/Metadata#" + id);
		} catch (URISyntaxException e) {
			e.printStackTrace();
			return null;
		}
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.rdf.Statement#setDatatype(java.lang.String)
	 */
	public boolean setDatatype(String datatype) {
		return updateField(METADATA_DATATYPE, datatype, Types.VARCHAR);
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.Identifiable#setDescription(java.lang.String)
	 */
	public void setDescription(String description) throws UpdateFailedException {
		// no need to be implemented
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.Identifiable#setLabel(java.lang.String)
	 */
	public void setLabel(String label) {
		// no need to be implemented
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.rdf.Statement#setObject(java.lang.String)
	 */
	public boolean setObject(String object) {
		return updateField(METADATA_OBJECT, object, Types.LONGVARCHAR);
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.rdf.Statement#setProperty(java.lang.String)
	 */
	public boolean setProperty(String property) {
		return updateField(METADATA_PROPERTY, property, Types.VARCHAR);
	}

	/* (non-Javadoc)
	 * @see net.groupme.model.rdf.Statement#setSubject(net.groupme.model.Resource)
	 */
	public boolean setSubject(Resource resource) {
		String resID = null;
		resID = resource.getId().trim();
		if(resID.startsWith("#")){
			resID = resID.substring(1);
		}
		return updateField(METADATA_RESOURCE_ID, resID, Types.INTEGER);
	}

	/**
	 * Small helper function which updates a field of this Statement.
	 * @param fieldName the attribute name within the Statement table
	 * @param value the new value
	 * @return returns <code>true</code> if the update was successfully, otherwise <code>false</code>
	 */
	private boolean updateField(String fieldName, String value, int type){
		try {
			SQLUpdateUtility.updateSingleFieldByID(ds, TABLE_METADATA, METADATA_ID, id, fieldName, value, type);
		} catch (UpdateFailedException e) {
			logger.error("Error on update " + e);
			e.printStackTrace();
			return false;
		}
		return true;
	}
}
